package com.canvas.service.house;

import com.canvas.base.HouseSort;
import com.canvas.base.HouseStatusEnum;
import com.canvas.base.LoginUserUtil;
import com.canvas.dto.*;
import com.canvas.enums.HouseStatus;
import com.canvas.form.DatatableSearch;
import com.canvas.form.HouseForm;
import com.canvas.form.RentSearch;
import com.canvas.pojo.*;
import com.canvas.repository.*;
import com.canvas.service.search.ISearchService;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.criteria.Predicate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @Author: 宗恣
 * @Date: 2018/9/4 0004 15:51
 */
@Service
public class IHouseServiceImpl implements IHouseService {

    @Autowired
    private HouseRepository houseRepository;

    @Autowired
    private HouseDetailRepository houseDetailRepository;

    @Autowired
    private SubwayRepository subwayRepository;

    @Autowired
    private SubwayStationRepository subwayStationRepository;

    @Autowired
    private HousePictureRepository housePictureRepository;

    @Autowired
    private HouseTagRepository houseTagRepository;

    @Autowired
    private IQiNiuService iQiNiuService;

    @Autowired
    private ISearchService iSearchService;

    @Autowired
    private ModelMapper modelMapper;

    @Value("${qiniu.cdn.prefix}")
    private String cdnPrefix;

    @Override
    @Transactional
    public ServiceResult<HouseDTO> save(HouseForm houseForm) {

        House house = new House();
        HouseDetail houseDetail = new HouseDetail();
        ServiceResult<HouseDTO> subwayValidtionResult = wrapperDetailInfo(houseDetail, houseForm);

        if (null != subwayValidtionResult) {
            return subwayValidtionResult;
        }

        modelMapper.map(houseForm, house);

        Date now = new Date();
        house.setCreateTime(now);
        house.setLastUpdateTime(now);
        house.setAdminId(LoginUserUtil.getLonginUserId());
        houseRepository.save(house);

        houseDetail.setHouseId(house.getId());
        houseDetail = houseDetailRepository.save(houseDetail);

        List<HousePicture> pictures = generatePictures(houseForm, house.getId());
        Iterable<HousePicture> housePictures = housePictureRepository.saveAll(pictures);

        HouseDTO houseDTO = modelMapper.map(house, HouseDTO.class);
        HouseDetailDTO houseDetailDTO = modelMapper.map(houseDetail, HouseDetailDTO.class);

        houseDTO.setHouseDetail(houseDetailDTO);

        List<HousePictureDTO> pictureDTOS = ((List<HousePicture>) housePictures).stream()
                .map(housePicture -> modelMapper.map(housePicture, HousePictureDTO.class))
                .collect(Collectors.toList());

        houseDTO.setPictures(pictureDTOS);
        houseDTO.setCover(this.cdnPrefix + houseDTO.getCover());

        List<String> tags = houseForm.getTags();
        if (tags != null && !tags.isEmpty()) {

            List<HouseTag> houseTags = tags.stream()
                    .map(tag -> new HouseTag(house.getId(), tag))
                    .collect(Collectors.toList());

            houseTagRepository.saveAll(houseTags);
            houseDTO.setTags(tags);
        }

        return new ServiceResult(true, null, houseDTO);
    }

    private List<HousePicture> generatePictures(HouseForm houseForm, Long houseId) {

        List<HousePicture> pictures = new ArrayList<>();

        if (houseForm.getPhotos() == null || houseForm.getPhotos().isEmpty()) {
            return pictures;
        }

        pictures = houseForm.getPhotos().stream()
                .map(photoForm -> new HousePicture(houseId, photoForm.getPath(), cdnPrefix, photoForm.getWidth(), photoForm.getHeight()))
                .collect(Collectors.toList());

        return pictures;

    }

    /**
     * 房源详细信息对象填充
     *
     * @param houseDetail
     * @param houseForm
     * @return
     */
    private ServiceResult<HouseDTO> wrapperDetailInfo(HouseDetail houseDetail, HouseForm houseForm) {

        Subway subway = subwayRepository.findById(houseForm.getSubwayLineId()).get();

        if (null == subway) {
            return new ServiceResult<>(false, "Not valid subway line!");
        }

        SubwayStation subwayStation = subwayStationRepository.findById(houseForm.getSubwayStationId()).get();

        if (null == subwayStation) {
            return new ServiceResult<>(false, "Not valid subway station!");
        }

        houseDetail.setSubwayLineId(subway.getId());
        houseDetail.setSubwayLineName(subway.getName());

        houseDetail.setSubwayStationId(subwayStation.getId());
        houseDetail.setSubwayStationName(subwayStation.getName());

        houseDetail.setDescription(houseForm.getDescription());
        houseDetail.setDetailAddress(houseForm.getDetailAddress());
        houseDetail.setLayoutDesc(houseForm.getLayoutDesc());
        houseDetail.setRentWay(houseForm.getRentWay());
        houseDetail.setRoundService(houseForm.getRoundService());
        houseDetail.setTraffic(houseForm.getTraffic());

        return null;
    }

    @Override
    public ServiceMultiResult<HouseDTO> adminQuery(DatatableSearch searchBody) {

        Sort sort = Sort.by(Sort.Direction.fromString(searchBody.getDirection()), searchBody.getOrderBy());

        int start = searchBody.getStart() / searchBody.getLength();

        Pageable pageable = PageRequest.of(start, searchBody.getLength(), sort);

        Specification<House> specification = (root, query, cb) -> {

            Predicate predicate = cb.equal(root.get("adminId"), LoginUserUtil.getLonginUserId());

            predicate = cb.and(predicate, cb.notEqual(root.get("status"), HouseStatusEnum.DELETED.getValue()));

            if (null != searchBody.getCity()) {
                predicate = cb.and(predicate, cb.equal(root.get("cityEnName"), searchBody.getCity()));
            }

            if (null != searchBody.getStatus()) {
                predicate = cb.and(predicate, cb.equal(root.get("status"), searchBody.getStatus()));
            }

            if (null != searchBody.getCreateTimeMin()) {
                predicate = cb.and(predicate, cb.greaterThanOrEqualTo(root.get("createTime"), searchBody.getCreateTimeMin()));
            }

            if (null != searchBody.getCreateTimeMax()) {
                predicate = cb.and(predicate, cb.lessThanOrEqualTo(root.get("createTime"), searchBody.getCreateTimeMax()));
            }

            if (null != searchBody.getTitle()) {
                predicate = cb.and(predicate, cb.like(root.get("title"), "%" + searchBody.getTitle() + "%"));
            }

            return predicate;
        };

        Page<House> houses = houseRepository.findAll(specification, pageable);

        List<HouseDTO> houseDTOS = houses.stream()
                .map(house -> {
                    HouseDTO houseDTO = modelMapper.map(house, HouseDTO.class);
                    houseDTO.setCover(this.cdnPrefix + house.getCover());
                    return houseDTO;
                })
                .collect(Collectors.toList());

        return new ServiceMultiResult<>(houses.getTotalElements(), houseDTOS);
    }

    @Override
    public ServiceResult<HouseDTO> findCompleteOne(Long id) {

        House house = houseRepository.findById(id).get();

        if (null == house) {
            return ServiceResult.notFound();
        }

        HouseDetail houseDetail = houseDetailRepository.findByHouseId(id);
        List<HousePicture> pictures = housePictureRepository.findAllByHouseId(id);

        HouseDetailDTO detailDTO = modelMapper.map(houseDetail, HouseDetailDTO.class);
        List<HousePictureDTO> pictureDTOS = pictures.stream()
                .map(housePicture -> modelMapper.map(housePicture, HousePictureDTO.class))
                .collect(Collectors.toList());

        List<HouseTag> tags = houseTagRepository.findAllByHouseId(id);
        List<String> tagList = tags.stream()
                .map(HouseTag::getName)
                .collect(Collectors.toList());

        HouseDTO houseDTO = modelMapper.map(house, HouseDTO.class);
        houseDTO.setHouseDetail(detailDTO);
        houseDTO.setPictures(pictureDTOS);
        houseDTO.setTags(tagList);

        return ServiceResult.of(houseDTO);
    }

    @Override
    @Transactional
    public ServiceResult update(HouseForm houseForm) {

        House house = houseRepository.findById(houseForm.getId()).get();
        if (null == house) {
            return ServiceResult.notFound();
        }

        HouseDetail houseDetail = houseDetailRepository.findByHouseId(house.getId());
        if (null == houseDetail) {
            return ServiceResult.notFound();
        }

        ServiceResult<HouseDTO> wrapperResult = wrapperDetailInfo(houseDetail, houseForm);
        if (null != wrapperResult) {
            return wrapperResult;
        }

        houseDetailRepository.save(houseDetail);

        List<HousePicture> pictures = generatePictures(houseForm, houseForm.getId());
        housePictureRepository.saveAll(pictures);

        if (null == houseForm.getCover()) {
            houseForm.setCover(house.getCover());
        }

        modelMapper.map(houseForm, house);
        house.setLastUpdateTime(new Date());
        houseRepository.save(house);

        if (house.getStatus() == HouseStatus.PASSES.getValue()) {
            iSearchService.index(house.getId());
        }

        return ServiceResult.success();
    }

    @Override
    public ServiceResult removePhoto(Long id) {

        HousePicture picture = housePictureRepository.findById(id).get();

        if (null == picture) {
            return ServiceResult.notFound();
        }

        try {
            Response response = this.iQiNiuService.delete(picture.getPath());
            if (response.isOK()) {
                housePictureRepository.deleteById(picture.getId());
                return ServiceResult.success();
            } else {
                return new ServiceResult(false, response.error);
            }
        } catch (QiniuException e) {
            e.printStackTrace();
            return new ServiceResult(false, e.getMessage());
        }
    }

    @Override
    @Transactional
    public ServiceResult updateCover(Long coverId, Long targetId) {

        HousePicture cover = housePictureRepository.findById(coverId).get();

        if (null == cover) {
            return ServiceResult.notFound();
        }

        houseRepository.updateCover(targetId, cover.getPath());

        return ServiceResult.success();
    }

    @Override
    @Transactional
    public ServiceResult addTag(Long houseId, String tag) {

        House house = houseRepository.findById(houseId).get();
        if (null == house) {
            return ServiceResult.notFound();
        }

        HouseTag houseTag = houseTagRepository.findByNameAndHouseId(tag, houseId);
        if (null != houseTag) {
            return new ServiceResult(false, "标签已存在");
        }

        houseTagRepository.save(new HouseTag(houseId, tag));

        return ServiceResult.success();
    }

    @Override
    public ServiceResult removeTag(Long houseId, String tag) {

        House house = houseRepository.findById(houseId).get();
        if (null == house) {
            return ServiceResult.notFound();
        }

        HouseTag houseTag = houseTagRepository.findByNameAndHouseId(tag, houseId);
        if (null == houseTag) {
            return new ServiceResult(false, "标签不存在");
        }

        houseTagRepository.deleteById(houseTag.getId());

        return ServiceResult.success();
    }

    @Override
    @Transactional
    public ServiceResult updateStatus(Long id, int status) {

        House house = houseRepository.findById(id).get();
        if (null == house) {
            return ServiceResult.notFound();
        }

        if (house.getStatus() == status) {
            return new ServiceResult(false, "状态没有发生变化");
        }

        if (house.getStatus() == HouseStatus.RENTED.getValue()) {
            return new ServiceResult(false, "已出租的房源不允许修改状态");
        }

        if (house.getStatus() == HouseStatus.DELETED.getValue()) {
            return new ServiceResult(false, "已删除的资源不允许操作");
        }

        houseRepository.updateStatus(id, status);

        // 上架更新索引，其他情况删除索引
        if (status == HouseStatus.PASSES.getValue()) {
            iSearchService.index(id);
        } else {
            iSearchService.remove(id);
        }

        return ServiceResult.success();
    }

    private List<HouseDTO> wrapperHouseResult(List<Long> houseIds) {

        Map<Long, HouseDTO> idToHouseMap = new HashMap<>();
        List<House> houses = houseRepository.findAllById(houseIds);
        houses.forEach(house -> {
            HouseDTO houseDTO = modelMapper.map(house, HouseDTO.class);
            houseDTO.setCover(this.cdnPrefix + house.getCover());
            idToHouseMap.put(house.getId(), houseDTO);
        });

        wrapperHouseList(houseIds, idToHouseMap);

        // 矫正顺序
        List<HouseDTO> result = houseIds.stream()
                .map(id -> idToHouseMap.get(id))
                .collect(Collectors.toList());

        return result;
    }

    @Override
    public ServiceMultiResult<HouseDTO> query(RentSearch rentSearch) {

        // 如果关键词不为空，则走es逻辑
        if (null != rentSearch.getKeywords() && !rentSearch.getKeywords().isEmpty()) {
            ServiceMultiResult<Long> serviceResult = iSearchService.query(rentSearch);
            if (serviceResult.getTotal() == 0) {
                return new ServiceMultiResult<>(0, new ArrayList<>());
            }

            return new ServiceMultiResult<>(serviceResult.getTotal(), wrapperHouseResult(serviceResult.getResult()));

        }

        return simpleQuery(rentSearch);
    }

    private ServiceMultiResult<HouseDTO> simpleQuery(RentSearch rentSearch) {

        Sort sort = HouseSort.generateSort(rentSearch.getOrderBy(), rentSearch.getOrderDirection());

        int start = rentSearch.getStart() / rentSearch.getSize();

        Pageable pageable = PageRequest.of(start, rentSearch.getSize(), sort);

        Specification<House> specification = (root, criteriaQuery, criteriaBuilder) -> {
            Predicate predicate = criteriaBuilder.equal(root.get("status"), HouseStatus.PASSES.getValue());

            predicate = criteriaBuilder.and(predicate, criteriaBuilder.equal(root.get("cityEnName"), rentSearch.getCityEnName()));

            if (HouseSort.DISTANCE_TO_SUBWAY_KEY.equals(rentSearch.getOrderBy())) {
                predicate = criteriaBuilder.and(predicate, criteriaBuilder.gt(root.get(HouseSort.DISTANCE_TO_SUBWAY_KEY), -1));
            }

            return predicate;
        };

        Page<House> houses = houseRepository.findAll(specification, pageable);

        List<Long> houseIds = Lists.newArrayList();
        Map<Long, HouseDTO> idToHouseMap = Maps.newHashMap();
        List<HouseDTO> houseDTOS = houses.stream()
                .map(house -> {
                    HouseDTO houseDTO = modelMapper.map(house, HouseDTO.class);
                    houseDTO.setCover(this.cdnPrefix + house.getCover());

                    houseIds.add(house.getId());
                    idToHouseMap.put(house.getId(), houseDTO);

                    return houseDTO;
                })
                .collect(Collectors.toList());

        wrapperHouseList(houseIds, idToHouseMap);

        return new ServiceMultiResult<>(houses.getTotalElements(), houseDTOS);
    }

    /**
     * 渲染详细信息 及 标签
     *
     * @param houseIds
     */
    private void wrapperHouseList(List<Long> houseIds, Map<Long, HouseDTO> idToHouseMap) {

        List<HouseDetail> details = houseDetailRepository.findAllByHouseIdIn(houseIds);

        details.forEach(houseDetail -> {
            HouseDTO houseDTO = idToHouseMap.get(houseDetail.getHouseId());
            HouseDetailDTO detailDTO = modelMapper.map(houseDetail, HouseDetailDTO.class);
            houseDTO.setHouseDetail(detailDTO);
        });

        List<HouseTag> houseTags = houseTagRepository.findAllByHouseIdIn(houseIds);
        houseTags.forEach(houseTag -> {
            HouseDTO houseDTO = idToHouseMap.get(houseTag.getHouseId());
            houseDTO.getTags().add(houseTag.getName());
        });

    }

}
